const wu=require("./wuLib.js");
const {getZ}=require("./wuRestoreZ.js");
const {wxsBeautify}=require("./wuJs.js");
const fs=require('fs');
const path=require("path");
const esprima=require('esprima');
const {VM}=require('vm2');
const escodegen=require('escodegen');
function analyze(core,z,namePool,xPool,fakePool={},zMulName="0"){
	function anaRecursion(core,fakePool={}){
		return analyze(core,z,namePool,xPool,fakePool,zMulName);
	}
	function push(name,elem){
		namePool[name]=elem;
	}
	function pushSon(pname,son){
		if(fakePool[pname])fakePool[pname].son.push(son);
		else namePool[pname].son.push(son);
	}
	for(let ei=0;ei<core.length;ei++){let e=core[ei];
		switch(e.type){
			case "ExpressionStatement":
			{
				let f=e.expression;
				if(f.callee){
					if(f.callee.type=="Identifier"){
						switch(f.callee.name){
							case "_r":
								namePool[f.arguments[0].name].v[f.arguments[1].value]=z[f.arguments[2].value];
							break;
							case "_rz":
								namePool[f.arguments[1].name].v[f.arguments[2].value]=z.mul[zMulName][f.arguments[3].value];
							break;
							case "_":
								pushSon(f.arguments[0].name,namePool[f.arguments[1].name]);
							break;
							case "_2":
							{
								let item=f.arguments[6].value;//def:item
								let index=f.arguments[7].value;//def:index
								let data=z[f.arguments[0].value];
								let key=escodegen.generate(f.arguments[8]).slice(1,-1);//f.arguments[8].value;//def:""
								let obj=namePool[f.arguments[5].name];
								let gen=namePool[f.arguments[1].name];
								if(gen.tag=="gen"){
									let ret=gen.func.body.body.pop().argument.name;
									anaRecursion(gen.func.body.body,{[ret]:obj});
								}
								obj.v["wx:for"]=data;
								if(index!="index")obj.v["wx:for-index"]=index;
								if(item!="item")obj.v["wx:for-item"]=item;
								if(key!="")obj.v["wx:key"]=key;
							}
							break;
							case "_2z":
							{
								let item=f.arguments[7].value;//def:item
								let index=f.arguments[8].value;//def:index
								let data=z.mul[zMulName][f.arguments[1].value];
								let key=escodegen.generate(f.arguments[9]).slice(1,-1);//f.arguments[9].value;//def:""
								let obj=namePool[f.arguments[6].name];
								let gen=namePool[f.arguments[2].name];
								if(gen.tag=="gen"){
									let ret=gen.func.body.body.pop().argument.name;
									anaRecursion(gen.func.body.body,{[ret]:obj});
								}
								obj.v["wx:for"]=data;
								if(index!="index")obj.v["wx:for-index"]=index;
								if(item!="item")obj.v["wx:for-item"]=item;
								if(key!="")obj.v["wx:key"]=key;
							}
							break;
							case "_ic":
								pushSon(f.arguments[5].name,{tag:"include",son:[],v:{src:xPool[f.arguments[0].property.value]}});
							break;
							case "_ai":
							{//template import
								let to=Object.keys(fakePool)[0];
								if(to)pushSon(to,{tag:"import",son:[],v:{src:xPool[f.arguments[1].property.value]}});
								else throw Error("Unexpected fake pool");
							}
							break;
							case "_af":
							//ignore _af
							break;
							default:throw Error("Unknown expression callee name "+f.callee.name);
						}
					}else if(f.callee.type=="MemberExpression"){
						if(f.callee.object.name=="cs"||f.callee.property.name=="pop")break;
						throw Error("Unknown member expression");
					}else throw Error("Unknown callee type "+f.callee.type);
				}else if(f.type=="AssignmentExpression"&&f.operator=="="){
					//no special use
				}else throw Error("Unknown expression statement.");
				break;
			}
			case "VariableDeclaration":
				for(let dec of e.declarations){
					if(dec.init.type=="CallExpression"){
						switch(dec.init.callee.name){
							case "_n":
								push(dec.id.name,{tag:dec.init.arguments[0].value,son:[],v:{}});
							break;
							case "_v":
								push(dec.id.name,{tag:"block",son:[],v:{}});
							break;
							case "_o":
								push(dec.id.name,{tag:"__textNode__",content:z[dec.init.arguments[0].value]});
							break;
							case "_oz":
								push(dec.id.name,{tag:"__textNode__",content:z.mul[zMulName][dec.init.arguments[1].value]});
							break;
							case "_m":
							{
								if(dec.init.arguments[2].elements.length>0)
									throw Error("Noticable generics content: "+dec.init.arguments[2].toString());
								let mv={};
								let name=null,base=0;
								for(let x of dec.init.arguments[1].elements){
									let v=x.value;
									if(!v&&typeof v!="number"){
										if(x.type=="UnaryExpression"&&x.operator=="-")v=-x.argument.value;
										else throw Error("Unknown type of object in _m attrs array: "+x.type);
									}
									if(name===null){
										name=v;
									}else{
										if(base+v<0)mv[name]=null;else{
											mv[name]=z[base+v];
											if(base==0)base=v;
										}
										name=null;
									}
								}
								push(dec.id.name,{tag:dec.init.arguments[0].value,son:[],v:mv});
							}
							break;
							case "_mz":
							{
								if(dec.init.arguments[3].elements.length>0)
									throw Error("Noticable generics content: "+dec.init.arguments[3].toString());
								let mv={};
								let name=null,base=0;
								for(let x of dec.init.arguments[2].elements){
									let v=x.value;
									if(!v&&typeof v!="number"){
										if(x.type=="UnaryExpression"&&x.operator=="-")v=-x.argument.value;
										else throw Error("Unknown type of object in _mz attrs array: "+x.type);
									}
									if(name===null){
										name=v;
									}else{
										if(base+v<0)mv[name]=null;else{
											mv[name]=z.mul[zMulName][base+v];
											if(base==0)base=v;
										}
										name=null;
									}
								}
								push(dec.id.name,{tag:dec.init.arguments[1].value,son:[],v:mv});
							}
							break;
							case "_gd"://template use/is
							{
								let is=namePool[dec.init.arguments[1].name].content;
								let data=null,obj=null;
								ei++;
								for(let e of core[ei].consequent.body){
									if(e.type=="VariableDeclaration"){
										for(let f of e.declarations){
											if(f.init.type=="LogicalExpression"&&f.init.left.type=="CallExpression"){
												if(f.init.left.callee.name=="_1")data=z[f.init.left.arguments[0].value];
												else if(f.init.left.callee.name=="_1z")data=z.mul[zMulName][f.init.left.arguments[1].value];
											}
										}
									}else if(e.type=="ExpressionStatement"){
										let f=e.expression;
										if(f.type=="AssignmentExpression"&&f.operator=="="&&f.left.property&&f.left.property.name=="wxXCkey"){
											obj=f.left.object.name;
										}
									}
								}
								namePool[obj].tag="template";
								Object.assign(namePool[obj].v,{is:is,data:data});
							}
							break;
							default:
							{
								let funName=dec.init.callee.name;
								if(funName.startsWith("gz$gwx")){
									zMulName=funName.slice(6);
								}else throw Error("Unknown init callee "+funName);
							}
						}
					}else if(dec.init.type=="FunctionExpression"){
						push(dec.id.name,{tag:"gen",func:dec.init});
					}else if(dec.init.type=="MemberExpression"){
						if(dec.init.object.type=="MemberExpression"&&dec.init.object.object.name=="e_"&&dec.init.object.property.type=="MemberExpression"&&dec.init.object.property.object.name=="x"){
							if(dec.init.property.name=="j"){//include
								//do nothing
							}else if(dec.init.property.name=="i"){//import
								//do nothing
							}else throw Error("Unknown member expression declaration.");
						}else throw Error("Unknown member expression declaration.");
					}else throw Error("Unknown declaration init type " + dec.init.type);
				}
				break;
			case "IfStatement":
				if(e.test.callee.name.startsWith("_o")){
					function parse_OFun(e){
						if(e.test.callee.name=="_o")return z[e.test.arguments[0].value];
						else if(e.test.callee.name=="_oz")return z.mul[zMulName][e.test.arguments[1].value];
						else throw Error("Unknown if statement test callee name:"+e.test.callee.name);
					}
					let vname=e.consequent.body[0].expression.left.object.name;
					let nif={tag:"block",v:{"wx:if":parse_OFun(e)},son:[]};
					anaRecursion(e.consequent.body,{[vname]:nif});
					pushSon(vname,nif);
					if(e.alternate){
						while(e.alternate&&e.alternate.type=="IfStatement"){
							e=e.alternate;
							nif={tag:"block",v:{"wx:elif":parse_OFun(e)},son:[]};
							anaRecursion(e.consequent.body,{[vname]:nif});
							pushSon(vname,nif);
						}
						if(e.alternate&&e.alternate.type=="BlockStatement"){
							e=e.alternate;
							nif={tag:"block",v:{"wx:else":null},son:[]};
							anaRecursion(e.body,{[vname]:nif});
							pushSon(vname,nif);
						}
					}
				}else throw Error("Unknown if statement.");
				break;
			default:
				throw Error("Unknown type "+e.type);
		}
	}
}
function wxmlify(str,isText){
	if(typeof str=="undefined"||str===null)return "Empty";//throw Error("Empty str in "+(isText?"text":"prop"));
	if(isText)return str;//may have some bugs in some specific case(undocumented by tx)
	else return str.replace(/"/g, '\\"');
}
function elemToString(elem,dep,moreInfo=false){
	const longerList=[];//put tag name which can't be <x /> style.
	const indent=' '.repeat(4);
	function isTextTag(elem){
		return elem.tag=="__textNode__"&&elem.content;
	}
	function elemRecursion(elem,dep){
		return elemToString(elem,dep,moreInfo);
	}
	function trimMerge(rets){
		let needTrimLeft=false,ans="";
		for(let ret of rets){
			if(ret.textNode==1){
				if(!needTrimLeft){
					needTrimLeft=true;
					ans=ans.trimRight();
				}
			}else if(needTrimLeft){
				needTrimLeft=false;
				ret=ret.trimLeft();
			}
			ans+=ret;
		}
		return ans;
	}
	if(isTextTag(elem)){
		//In comment, you can use typify text node, which beautify its code, but may destroy ui.
		//So, we use a "hack" way to solve this problem by letting typify program stop when face textNode
		let str=new String(wxmlify(elem.content,true));
		str.textNode=1;
		return wxmlify(str,true);//indent.repeat(dep)+wxmlify(elem.content.trim(),true)+"\n";
	}
	if(elem.tag=="block"&&!moreInfo){
		if(elem.son.length==1&&!isTextTag(elem.son[0])){
			let ok=true,s=elem.son[0];
			for(let x in elem.v)if(x in s.v){
				ok=false;
				break;
			}
			if(ok&&!(("wx:for" in s.v||"wx:if" in s.v)&&("wx:if" in elem.v||"wx:else" in elem.v||"wx:elif" in elem.v))){//if for and if in one tag, the default result is an if in for. And we should block if nested in elif/else been combined.
				Object.assign(s.v,elem.v);
				return elemRecursion(s,dep);
			}
		}else if(Object.keys(elem.v).length==0){
			let ret=[];
			for(let s of elem.son)ret.push(elemRecursion(s,dep));
			return trimMerge(ret);
		}
	}
	let ret=indent.repeat(dep)+"<"+elem.tag;
	for(let v in elem.v)ret+=" "+v+(elem.v[v]!==null?"=\""+wxmlify(elem.v[v])+"\"":"");
	if(elem.son.length==0){
		if(longerList.includes(elem.tag))return ret+" />\n";
		else return ret+"></"+elem.tag+">\n";
	}
	ret+=">\n";
	let rets=[ret];
	for(let s of elem.son)rets.push(elemRecursion(s,dep+1));
	rets.push(indent.repeat(dep)+"</"+elem.tag+">\n");
	return trimMerge(rets);
}
function doWxml(state,dir,name,code,z,xPool,rDs,wxsList,moreInfo){
	let rname=code.slice(code.lastIndexOf("return")+6).replace(/[\;\}]/g,"").trim();
	code=code.slice(code.indexOf("\n"),code.lastIndexOf("return")).trim();
	let r={son:[]};
	analyze(esprima.parseScript(code).body,z,{[rname]:r},xPool,{[rname]:r});
	let ans=[];
	for(let elem of r.son)ans.push(elemToString(elem,0,moreInfo));
	let result=[ans.join("")];
	for(let v in rDs){
		state[0]=v;
		let oriCode=rDs[v].toString();
		let rname=oriCode.slice(oriCode.lastIndexOf("return")+6).replace(/[\;\}]/g,"").trim();
		let tryPtr=oriCode.indexOf("\ntry{");
		let zPtr=oriCode.indexOf("var z=gz$gwx");
		let code=oriCode.slice(tryPtr+5,oriCode.lastIndexOf("\n}catch(")).trim();
		if(zPtr!=-1&&tryPtr>zPtr){
			let attach=oriCode.slice(zPtr);
			attach=attach.slice(0,attach.indexOf("()"))+"()\n";
			code=attach+code;
		}
		let r={tag:"template",v:{name:v},son:[]};
		analyze(esprima.parseScript(code).body,z,{[rname]:r},xPool,{[rname]:r});
		result.unshift(elemToString(r,0,moreInfo));
	}
	name=path.resolve(dir,name);
	if(wxsList[name])result.push(wxsList[name]);
	wu.save(name,result.join(""));
}
function tryWxml(dir,name,code,z,xPool,rDs,...args){
	console.log("Decompile "+name+"...");
	let state=[null];
	try{
		doWxml(state,dir,name,code,z,xPool,rDs,...args);
		console.log("Decompile success!");
	}catch(e){
		console.log("error on "+name+"("+(state[0]===null?"Main":"Template-"+state[0])+")\nerr: ",e);
		if(state[0]===null)wu.save(path.resolve(dir,name+".ori.js"),code);
		else wu.save(path.resolve(dir,name+".tem-"+state[0]+".ori.js"),rDs[state[0]].toString());
	}
}
function doWxs(code){
	const before='nv_module={nv_exports:{}};';
    return wxsBeautify(code.slice(code.indexOf(before)+before.length,code.lastIndexOf('return nv_module.nv_exports;}')).replace(/nv\_/g,''));
}
function doFrame(name,cb,order){
	let moreInfo=order.includes("m");
	wxsList={};
	wu.get(name,code=>{
		getZ(code,z=>{
			const before="\nvar nv_require=function(){var nnm=";
			code=code.slice(code.indexOf(before)+before.length,code.lastIndexOf("if(path&&e_[path]){"));
			json=code.slice(0,code.indexOf("};")+1);
			let endOfRequire=code.indexOf("()\r\n")+4;
			if(endOfRequire==4-1)endOfRequire=code.indexOf("()\n")+3;
			code=code.slice(endOfRequire);
			let rD={},rE={},rF={},requireInfo,x,vm=new VM({sandbox:{d_:rD,e_:rE,f_:rF,_vmRev_(data){
				[x,requireInfo]=data;
			},nv_require(path){
				return ()=>path;
			}}});
			vm.run(code+"\n_vmRev_([x,"+json+"])");
			let dir=path.dirname(name),pF=[];
			for(let info in rF)if(typeof rF[info]=="function"){
				let name=path.resolve(dir,(info[0]=='/'?'.':'')+info),ref=rF[info]();
				pF[ref]=info;
				wu.save(name,doWxs(requireInfo[ref].toString()));
			}
			for(let info in rF)if(typeof rF[info]=="object"){
				let name=path.resolve(dir,(info[0]=='/'?'.':'')+info);
				let res=[],now=rF[info];
				for(let deps in now){
					let ref=now[deps]();
					if(ref.includes(":"))res.push("<wxs module=\""+deps+"\">\n"+doWxs(requireInfo[ref].toString())+"\n</wxs>");
					else res.push("<wxs module=\""+deps+"\" src=\""+wu.toDir(pF[ref],info)+"\" />");
					wxsList[name]=res.join("\n");
				}
			}
			for(let name in rE)tryWxml(dir,name,rE[name].f.toString(),z,x,rD[name],wxsList,moreInfo);
			cb({[name]:4});
		});
	});
}
module.exports={doFrame:doFrame};
if(require.main===module){
    wu.commandExecute(doFrame,"Restore wxml files.\n\n<files...>\n\n<files...> restore wxml file from page-frame.html or app-wxss.js.");
}
